<?php

if (!defined('TKG')) {
	exit();
}

function disable_magic_quotes() {
	if (get_magic_quotes_gpc()) {
		$process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
		while (list($key, $val) = each($process)) {
			foreach ($val as $k => $v) {
				unset($process[$key][$k]);
				if (is_array($v)) {
					$process[$key][stripslashes($k)] = $v;
					$process[] = &$process[$key][stripslashes($k)];
				} else {
					$process[$key][stripslashes($k)] = stripslashes($v);
				}
			}
		}
		unset($process);
	}
}

function generate_bin_file($matrix_rows, $matrix_cols, $matrix_size, $max_layers, $max_fns, $keymaps, $fn_actions, $eep_size, $eep_start, $additional) {
	// generate binary data
	$keymap_bin = '';
	// fn actions
	for ($fn = 0; $fn < $max_fns; $fn++) {
		$keymap_bin .= pack('v', isset($fn_actions[$fn]) ? $fn_actions[$fn] : 0);
	}
	// keymaps
	for ($layer = 0; $layer < $max_layers; $layer++) {
		for ($row = 0; $row < $matrix_rows; $row++) {
			for ($col = 0; $col < $matrix_cols; $col++) {
				$keymap_bin .= pack('C', isset($keymaps[$layer][$row][$col]) ? $keymaps[$layer][$row][$col] : 0);
			}
		}
		if ($matrix_size && $matrix_size > $matrix_rows * $matrix_cols) {
			$keymap_bin .= str_repeat(pack('C', 0), $matrix_size - $matrix_rows * $matrix_cols);
		}
	}
	// checksum
	$checksum = 0xFEED;
	$checksum = calc_checksum_word($keymap_bin, $checksum);
	$keymap_bin = pack('v', $checksum) . $keymap_bin;

	// fill eeprom
	$eep_bin = '';
	$eep_bin .= str_repeat(pack('C', 0xFF), $eep_start);
	$eep_bin .= $keymap_bin;
	$eep_bin .= str_repeat(pack('C', 0xFF), $eep_size - $eep_start - strlen($keymap_bin));

	// overwrite additional data
	$eep_bin = process_additional($eep_bin, $additional);

	return $eep_bin;
}

function generate_eep_file($matrix_rows, $matrix_cols, $matrix_size, $max_layers, $max_fns, $keymaps, $fn_actions, $eep_size, $eep_start, $additional) {
	return bin_to_intel_hex(generate_bin_file($matrix_rows, $matrix_cols, $matrix_size, $max_layers, $max_fns, $keymaps, $fn_actions, $eep_size, $eep_start, $additional));
}

function generate_c_header() {
	// generate header
	return "// Generated by TKG at " . date("Y-m-d H:i:s"). "\n\n";
}

function generate_c_file($matrix_rows, $matrix_cols, $max_layers, $max_fns, $keymaps, $fn_actions) {
	$file = "";
	$file .= "#include \"keymap_common.h\"\n\n";
	// generate keymap macro
	$macro_name = "KEYMAP_TKG";
	$blanks = parse_blank_entries($keymaps);
	$file .= generate_keymap_macro($macro_name, $matrix_rows, $matrix_cols, $blanks);
	// keymaps block
	$file .= <<<EOF
#ifdef KEYMAP_SECTION_ENABLE
const uint8_t keymaps[][MATRIX_ROWS][MATRIX_COLS] __attribute__ ((section (".keymap.keymaps"))) = {
#else
const uint8_t keymaps[][MATRIX_ROWS][MATRIX_COLS] PROGMEM = {
#endif

EOF;
	$file .= generate_keymaps_content($macro_name, $matrix_rows, $matrix_cols, $keymaps, $blanks);
	$file .= "};\n\n";
	// fn_actions block
	$file .= <<<EOF
#ifdef KEYMAP_SECTION_ENABLE
const uint16_t fn_actions[] __attribute__ ((section (".keymap.fn_actions"))) = {
#else
const uint16_t fn_actions[] PROGMEM = {
#endif

EOF;
	$file .= generate_fn_actions_content($fn_actions);
	$file .= "};";
	return $file;
}

function check_cache($subdir, $hash) {
	$filename = join(DIRECTORY_SEPARATOR, array(CACHE_DIR, $subdir, $hash));
	if (file_exists($filename)) {
		return file_get_contents($filename);
	}
	else {
		return NULL;
	}
}

function write_cache($subdir, $hash, $file) {
	$dir = join(DIRECTORY_SEPARATOR, array(CACHE_DIR, $subdir)) . '/';
	if (is_writable($dir)) {
		$filename = join(DIRECTORY_SEPARATOR, array(CACHE_DIR, $subdir, $hash));
		file_put_contents($filename, $file);
	}
}

function calc_checksum_word($bin, $checksum = 0) {
	for ($i = 0; $i < strlen($bin); $i += 2) {
		list(,$word) = unpack('v', substr($bin, $i, 2));
		$checksum = ($checksum + $word) % 0x10000;
	}
	return $checksum;
}

function process_additional($bin, $addl) {
	for ($i = 0; $i < count($addl); $i++) {
		$block = $addl[$i];
		if (isset($block["start"]) && isset($block["size"]) && isset($block["data"])) {
			$start = $block["start"];
			$size = $block["size"];
			$data = $block["data"];
			$format = "C";
			$sizeof = 1;
			if (isset($block["type"])) {
				$type = $block["type"];
				if ($type == "byte") {
					$format = "C";
					$sizeof = 1;
				}
				else if ($type == "word") {
					$format = "v";
					$sizeof = 2;
				}
			}
			for ($j = 0; $j < count($data); $j++) {
				$array = pack($format, $data[$j]);
				for ($k = 0; $k < $sizeof; $k++) {
					$bin[$start + $j * $sizeof + $k] = $array[$k];
				}
			}
		}
	}
	return $bin;
}

function bin_to_intel_hex($bin, $byte_count = 16) {
	$hex = '';
	for ($i = 0; $i < strlen($bin); $i += $byte_count) {
		$block = substr($bin, $i, $byte_count);
		$row = sprintf("%02X%04X00%s",
			$byte_count,
			(int)($i / $byte_count) * $byte_count,
			strtoupper(bin2hex($block)));
		$bytes = unpack('C*', pack("H*", $row));
		$checksum = 0;
		foreach ($bytes as $byte) {
			$checksum = ($checksum + $byte) % 0x100;
		}
		$checksum = (0x100 - $checksum) % 0x100;
		$hex .= sprintf(":%s%02X\r\n", $row, $checksum);
	}
	$hex .= ":00000001FF\r\n";
	return $hex;
}

function parse_blank_entries($matrices) {
	$blank_symbol = "KC_NO";
	$blanks = array();
	foreach ($matrices as $matrix) {
		for ($row = 0; $row < count($matrix); $row++) {
			for ($col = 0; $col < count($matrix[$row]); $col++) {
				if ($matrix[$row][$col] == $blank_symbol) {
					array_push($blanks, "$row,$col");
				}
			}
		}
		break;
	}
	foreach ($matrices as $matrix) {
		$alt_blanks = array();
		foreach ($blanks as $blank) {
			list($row, $col) = explode(',', $blank);
			if ($matrix[$row][$col] == $blank_symbol) {
				array_push($alt_blanks, $blank);
			}
		}
		$blanks = $alt_blanks;
	}
	return $blanks;
}

function join_with_func_glur() {
	$params = func_get_args();
	$callback = array_shift($params);
	$array = array_shift($params);
	$count = count($array);
	$find_next = array_shift($params);
	if (is_null($find_next)) {
		$find_next = function($array, $index) {
			if ($index + 1 < count($array)) {
				return $array[$index + 1];
			}
			else {
				return null;
			}
		};
	}
	$join = '';
	$current = '';
	$next = '';
	for ($i = 0; $i < $count; $i++) {
		$join .= $array[$i];
		$current = $array[$i];
		$next = call_user_func($find_next, $array, $i);
		$join .= call_user_func_array($callback, array_merge(array($current, $next), $params));
	}
	return $join;
}

function find_next_non_empty($array, $index) {
	for ($i = $index + 1; $i < count($array); $i++) {
		if (!str_empty($array[$i])) {
			return $array[$i];
		}
	}
	return null;
}

function str_empty($string) {
	return (empty($string) && $string !== '0');
}

function str_patch($input, $length, $patch_string = " ") {
	return str_repeat($patch_string, $length - strlen($input));
}

function generate_keymap_macro($macro_name, $matrix_rows, $matrix_cols, $blank_entries = array()) {
	// prepare symbols
	$symbols = array();
	$matrix_cols_trimmed = 0;
	for ($row = 0; $row < $matrix_rows; $row++) {
		array_push($symbols, array());
		$last = 0;
		for ($col = 0; $col < $matrix_cols; $col++) {
			if (!in_array("$row,$col", $blank_entries)) {
				$symbols[$row][$col] = "K$row" . chr(ord("A") + $col);
				$last = $col;
			}
			else {
				$symbols[$row][$col] = "";
			}
		}
		$matrix_cols_trimmed = max($matrix_cols_trimmed, $last + 1);
	}
	// generate macro
	$macro = "#define $macro_name( \\\n    ";
	$macro .= join_with_func_glur(function($current, $next, $width) {
			if (is_null($next)) {
				return "";
			}
			else {
				$glur = preg_match('/,\s*$/', $current) ? " " : ",";
				return $glur . str_patch($current, $width - 1) . " \\\n    ";
			}
		}, array_map(function($array) {
			return join_with_func_glur(function($current, $next) {
				if (is_null($next)) {
					return "";
				}
				else {
					$glue = str_empty($current) ? " " : ",";
					return $glue . str_patch($current, 4);
				}
			}, $array, function($array, $index) {
				return find_next_non_empty($array, $index);
			});
		}, $symbols), function($array, $index) {
			return find_next_non_empty($array, $index);
		}, $matrix_cols_trimmed * 5 - 1);
	$macro .= "  \\\n) { \\\n    { ";
	$macro .= join_with_func_glur(function($current, $next, $width) {
			if (is_null($next)) {
				return "";
			}
			else {
				return str_patch($current, $width - 1) . " }, \\\n    { ";
			}
		}, array_map(function($array) {
			return join_with_func_glur(function($current, $next) {
				if (is_null($next)) {
					return str_patch($current, 8);
				}
				else {
					return "," . str_patch($current, 9);
				}
			}, array_map(function($val) {
				return str_empty($val) ? "KC_NO" : "KC_##$val";
			}, $array), function($array, $index) {
				return find_next_non_empty($array, $index);
			});
		}, $symbols), function($array, $index) {
			return find_next_non_empty($array, $index);
		}, $matrix_cols * 10 - 1);
	$macro .= " }  \\\n}\n\n";
	return $macro;
}

function generate_keymaps_content($macro_name, $matrix_rows, $matrix_cols, $matrices, $blank_entries = array()) {
	// prepare symbols
	$matrix_cols_trimmed = 0;
	for ($row = 0; $row < $matrix_rows; $row++) {
		$last = 0;
		for ($col = 0; $col < $matrix_cols; $col++) {
			if (!in_array("$row,$col", $blank_entries)) {
				$last = $col;
			}
		}
		$matrix_cols_trimmed = max($matrix_cols_trimmed, $last + 1);
	}
	foreach ($matrices as &$matrix) {
		foreach ($blank_entries as $blank) {
			list($row,$col) = explode(",", $blank);
			$matrix[$row][$col] = "";
		}
	}
	unset($matrix);
	// generate array content
	$content = "";
	foreach ($matrices as $layer => $matrix) {
		$content .= "    [$layer] = $macro_name(\n        ";
		$content .= join_with_func_glur(function($current, $next, $width) {
			if (is_null($next)) {
				return "";
			}
			else {
				$glur = preg_match('/,\s*$/', $current) ? " " : ",";
				return $glur . str_patch($current, $width) . " \\\n        ";
			}
		}, array_map(function($array) {
			return join_with_func_glur(function($current, $next) {
				if (is_null($next)) {
					return "";
				}
				else {
					$glue = str_empty($current) ? " " : ",";
					return $glue . str_patch($current, 4);
				}
			}, array_map(function($val) {
				return substr($val, 3);
			}, $array), function($array, $index) {
				return find_next_non_empty($array, $index);
			});
		}, $matrix), function($array, $index) {
			return find_next_non_empty($array, $index);
		}, $matrix_cols_trimmed * 5 - 1);
		$content .= "),\n";
	}
	return $content;
}

function generate_fn_actions_content($fn_actions) {
	$content = "";
	foreach ($fn_actions as $index => $fn) {
		$action = array_shift($fn);
		$params = $fn;
		$lr = "L";
		$content .= "    [$index] = $action(";
		$content .= join_with_func_glur(function($current, $next) {
			if ($current == "" || is_null($next)) {
				return "";
			}
			else {
				return ", ";
			}
		}, array_map(function($val) use (&$lr) {
			if ($val == "LR_LEFT") {
				$lr = "L";
				return "";
			}
			else if ($val == "LR_RIGHT") {
				$lr = "R";
				return "";
			}
			else if (is_array($val)) {
				return join(" | ", array_map(function($val) use ($lr) {
					return substr($val, 0, 4) . $lr . substr($val, 4);
				}, $val));
			}
			return $val;
		}, $params));
		$content .= "),\n";
	}
	return $content;
}

?>
